import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Scanner;

//This class should serve as the "server" portion of a connection
public class DTNListener implements Runnable {

	private MessageStatistics messages;
	DTNMessageManager messageManager;
	private int port;
	private ServerSocket listener;
	private Socket clientSocket;
	private boolean listening;
	private String NodeID;
	private ArrayList<String> partnerIDs;
	private ArrayList<String> prophetIDs;
	private double threshold;
	String partnerNodeID;

	private void Report(String message){
		System.out.println(NodeID + "[DTNListener] - " + message);
	}

	public DTNListener(String NodeID, int port, MessageStatistics messages, double threshold){
		messageManager = new DTNMessageManager(messages, threshold);
		this.threshold = threshold;
		this.messages = messages;
		this.port = port;
		listener = null;
		clientSocket = null;
		listening = true;
		this.NodeID = NodeID;
		partnerIDs = null;
		prophetIDs =null;
	}

	public void close(){
		listening = false;
	}

	public void run(){
		Report("Started.");
		try{
			listener = new ServerSocket(port);      
		}
		catch (IOException e){
			System.out.println("Server - Could not listen on port " + port);
			System.exit(-1);
		}
		PrintWriter out = null;
		BufferedReader in = null;

		while (listening){
			partnerIDs = null;
			prophetIDs = null;

			try{
				listener.setSoTimeout(2000);
				clientSocket = listener.accept();

			}
			catch (IOException e){
				clientSocket = null;
			}
			if (clientSocket != null){
				try{
					out = new PrintWriter(clientSocket.getOutputStream(), true);
				}
				catch (IOException e){
					out = null;
				}

				try{
					in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
				}
				catch (IOException e){
					in = null;
				}

				if ((out != null) && (in != null)){
					ServeClient(in, out);


					messages.ageAllProphet();

					messageManager.reportDeliveredMessages(NodeID);
					messageManager.reportUndeliveredMessages(NodeID);
					messageManager.reportMessageBuffer(NodeID);

				} else {
					if (out != null){
						try{
							out.close();
						} catch (Exception e){

						}
					}
					if (in != null){
						try{
							in.close();
						} catch (Exception e){

						}
					}
					out = null;
					in = null;
				}


				try{
					clientSocket.close();
				} catch (Exception e){

				}


			}
			clientSocket = null;
		}






		if (listener != null){
			try{
				listener.close();
				listener = null;
			}
			catch (Exception e){
				//System.exit(-1);
			}

		}
		Report("Stopped.");

	}








	private void ServeClient(BufferedReader in, PrintWriter out){
		final int UNINITIALIZED = 0;
		final int INITIALIZED = 1;
		final int CLOSED = 2;
		final int ERROR = 3;
		int status = UNINITIALIZED;
		String line;
		//String partnerNodeID = "";

		Scanner commandScanner;
		String command;
		try{
			int failures = 0;
			while (status == UNINITIALIZED){
				out.println("NODE " + NodeID);
				if (in.ready()){
					line = in.readLine();
					//System.out.println("Server received: " + line);
					commandScanner = new Scanner(line);
					if (commandScanner.hasNext()){
						command = commandScanner.next();
						if (!command.equalsIgnoreCase("NODE")){
							line = in.readLine();
						} else {
							if (line.length() > 5){
								partnerNodeID = line.substring(5);

								messages.updateProphet(partnerNodeID);

							} else {
								line = in.readLine();
							}
						}
					}

					if (!partnerNodeID.equalsIgnoreCase("")){
						status = INITIALIZED;
						Report("Connected to Node " + partnerNodeID);
					}
				} else {
					if (failures >= 10){
						return;
					}
					failures++;
					Thread.sleep(Math.round(Math.random()*50)+50);
				}

			}
			while (status == INITIALIZED){
				line = in.readLine();
				commandScanner = new Scanner(line);
				if (commandScanner.hasNext()){
					command = commandScanner.next();
					if (command.equalsIgnoreCase("PROPHET")){

						if (!exchangeProphet(in,out)){
							out.println("CLOSE");
							status = ERROR;
						} else {
							status = INITIALIZED;
						}


					}
					if (command.equalsIgnoreCase("IDS")){

						if (!exchangeMessageIDs(in,out)){
							out.println("CLOSE");
							status = ERROR;
						} else {
							status = INITIALIZED;
						}


					}
					if (command.equalsIgnoreCase("REQUEST")){

						if (!exchangeMessages(in,out)){
							out.println("CLOSE");
							status = ERROR;
						} else {
							status = INITIALIZED;
						}

					}

					if (command.equalsIgnoreCase("CLOSE")){
						out.println("CLOSE");
						status = CLOSED;
					}

				}
				//System.out.println(status);
			}

			messages.deliverMessages(partnerNodeID);
			messages.DecrementAllTTL();


		} catch (Exception e){
		}

	}

	private boolean exchangeProphet(BufferedReader in, PrintWriter out) throws IOException{
		out.println("PROPHET");
		out.print(messages.AllProphets());
		out.println("ENDPROPHET");
		Report("Sent Prophet Statistics to " + partnerNodeID + "\n____________________________\n" + messages.AllProphets() + "____________________________");
		Scanner lineScanner;
		String line = "";
		prophetIDs = new ArrayList<String>();
		ArrayList<String> keys = new ArrayList<String>();
		ArrayList<Double> values = new ArrayList<Double>();
		String key;
		double value;
		while (!line.equalsIgnoreCase("ENDPROPHET")){
			while ((line.equalsIgnoreCase("PROPHET")) || (line.equalsIgnoreCase(""))){
				line = in.readLine();
			}
			//System.out.println(line);
			lineScanner = new Scanner(line);
			if (lineScanner.hasNext()){
				key = lineScanner.next();
				if (lineScanner.hasNextDouble()){
					value = lineScanner.nextDouble();
					keys.add(key);
					values.add(value);
				}
			}
			line = in.readLine();
			lineScanner.close();
		}
		if (keys.size() != values.size()){
			//System.out.println("MISMATCH");
			return false;
		}
		String received = "";
		ArrayList<String> prophetKeys = messages.getProphetKeys();
		for (int i = 0; i<prophetKeys.size();i++){
			if (!keys.contains(prophetKeys.get(i))){
				prophetIDs.add(prophetKeys.get(i));                     
			}
		}
		if (!prophetIDs.contains(NodeID)){
			prophetIDs.add(NodeID);
		}
		for (int i = 0; i<keys.size(); i++){
			if (messages.getProphet(keys.get(i)) > values.get(i)){
				prophetIDs.add(keys.get(i));
			}
			updateProphet(keys.get(i), values.get(i));
			received = received + keys.get(i) + " " + values.get(i) + "\n";
		}
		Report("Received Prophet Statistics from " + partnerNodeID + "\n____________________________\n" + received + "____________________________");

		return true;
	}

	private void updateProphet(String key, Double value){
		if (!key.equalsIgnoreCase(NodeID)){
			messages.transitiveProphetUpdate(key, partnerNodeID, value );
		}

	}


	private boolean exchangeMessageIDs(BufferedReader in, PrintWriter out) throws IOException{
		//System.out.println("Server Sends Messages");
		out.println("IDS");
		out.println(messages.ActiveMessageIDs());
		out.println("ENDIDS");
		Report("Sent Message IDs to " + partnerNodeID + "\n____________________________\n" + messages.ActiveMessageIDs() + "____________________________");

		//Scanner lineScanner;
		String line = "";
		ArrayList<String> IDs = new ArrayList<String>();
		//String ID;
		while (!line.equalsIgnoreCase("ENDIDS") && (line != null)){
			line = in.readLine();
			//System.out.println("SERVER reads a line");
			//System.out.println("SERVER read " + line);
			while ((line.equalsIgnoreCase("IDS")) || (line.equalsIgnoreCase(""))){
				//System.out.println("SERVER reads a line");
				line = in.readLine();
				//System.out.println("SERVER read " + line);

			}
			if (!line.equalsIgnoreCase("ENDIDS")){
				IDs.add(line);
			}

		}
		Report("Received Message IDs from " + partnerNodeID + "\n____________________________\n" + IDs + "\n____________________________");

		//System.out.println("SERVER Made it out");
		partnerIDs = new ArrayList<String>(IDs);
		//System.out.println("SERVER IDs\n" + partnerIDs.toString());
		return true;
	}



	private boolean exchangeMessages(BufferedReader in, PrintWriter out) throws IOException{
		String line = in.readLine();
		Scanner commandScanner;
		String command;
		while (!(line.equalsIgnoreCase("ENDREQUEST")) && (line != null)){
			commandScanner = new Scanner(line);
			if (commandScanner.hasNext()){
				command = commandScanner.next();
				if (command.equalsIgnoreCase("MESSAGE")){
					if (line.length() > 8){
						if (messages.getMessage(line.substring(8)) == null){
							out.println("NOMESSAGE");
						} else {
							out.println("PROVIDE " + line.substring(8));
							out.print(messages.getMessage(line.substring(8)));
							out.println("ENDPROVIDE " + line.substring(8));
						}
					}
				}
			}
			//out.println("Server received: " + line);
			line = in.readLine();
			commandScanner.close();
		}
		return requestMessages(in,out);
	}

	private String parseDestination(String messageID){
		String destination = "";
		Scanner IDScanner = new Scanner(messageID);
		if(IDScanner.hasNext()){
			IDScanner.next();
			if (IDScanner.hasNext()){
				IDScanner.next();
				if (IDScanner.hasNext()){
					destination = IDScanner.next();
				}
			}
		}
		IDScanner.close();
		return destination;
	}
	private boolean ProphetRequest(BufferedReader in, PrintWriter out) throws IOException{
		out.println("REQUEST");
		String receivedMessage;
		Scanner commandScanner;
		String command;
		for(int i = 0; i < partnerIDs.size(); i++){
			if (prophetIDs.contains(parseDestination(partnerIDs.get(i)))){
				receivedMessage = "";
				if (messages.getMessage(partnerIDs.get(i)) == null){
					out.println("MESSAGE " + partnerIDs.get(i));
					String line = in.readLine();
					commandScanner = new Scanner(line);
					if (commandScanner.hasNext()){
						command = commandScanner.next();
						if (command.equalsIgnoreCase("PROVIDE")){
							while (!command.equalsIgnoreCase("ENDPROVIDE")){
								line = in.readLine();
								commandScanner = new Scanner(line);
								if (commandScanner.hasNext()){
									command = commandScanner.next();
								}
								if (!command.equalsIgnoreCase("ENDPROVIDE")){
									if (!receivedMessage.equals("")){
										receivedMessage = receivedMessage + "\n";
									}
									receivedMessage = receivedMessage + line;
								}
							}
						}
					}
					//System.out.println(NodeID + " Got Message --" + receivedMessage + "--");
					addMessage(receivedMessage);
					commandScanner.close();
				}
			}
		}
		out.println("ENDREQUEST");
		return true;
	}

	private void addMessage(String receivedMessage){
		String source;
		String destination;
		int sequence;
		int TTL;
		Scanner messageParser = new Scanner(receivedMessage);
		messageParser.useDelimiter(";");
		if (messageParser.hasNext()){
			source = messageParser.next();
			if (messageParser.hasNext()){
				destination = messageParser.next();
				if (messageParser.hasNextInt()){
					sequence = messageParser.nextInt();
					if (messageParser.hasNextInt()){
						TTL = messageParser.nextInt();
						messages.AddMessage(source, destination, sequence, TTL);
					}
				}
			}
		}
		messageParser.close();
		//System.out.println(NodeID + " SERVER MESSAGE\n" + receivedMessage);
	}

	private boolean ERPRequest(BufferedReader in, PrintWriter out) throws IOException{
		String receivedMessage;
		Scanner commandScanner;
		String command;
		//System.out.println(NodeID + " has messages\n" + messages.AllMessageIDs() + "_________");
		out.println("REQUEST");
		for(int i = 0; i < partnerIDs.size(); i++){
			receivedMessage = "";
			//System.out.print(NodeID + " examining " + partnerIDs.get(i) + "-");
			if (messages.getMessage(partnerIDs.get(i)) == null){
				//System.out.print(" retrieve");
				out.println("MESSAGE " + partnerIDs.get(i));
				String line = in.readLine();
				commandScanner = new Scanner(line);
				if (commandScanner.hasNext()){
					command = commandScanner.next();
					if (command.equalsIgnoreCase("PROVIDE")){
						while (!command.equalsIgnoreCase("ENDPROVIDE")){
							line = in.readLine();
							commandScanner = new Scanner(line);
							if (commandScanner.hasNext()){
								command = commandScanner.next();
							}
							if (!command.equalsIgnoreCase("ENDPROVIDE")){
								if (!receivedMessage.equals("")){
									receivedMessage = receivedMessage + "\n";
								}
								receivedMessage = receivedMessage + line;
							}
						}
					}
				}
				//System.out.println();
				//System.out.println(NodeID + " Got Message --" + receivedMessage + "--");
				addMessage(receivedMessage);
				commandScanner.close();
			}


		}
		out.println("ENDREQUEST");
		return true;
	}

	private boolean requestMessages(BufferedReader in, PrintWriter out) throws IOException{
		if (messages.averageProphet() < threshold){
			Report("Average Prophet value = " + messages.averageProphet() + " < threshold (" + threshold + "). Using ERP.");
			messageManager.reportPropagationType(NodeID, partnerNodeID, 2);
			return ERPRequest(in, out);             
		} else {
			Report("Average Prophet value = " + messages.averageProphet() + " >= threshold (" + threshold + "). Using Prophet.");
			messageManager.reportPropagationType(NodeID, partnerNodeID, 1);
			return ProphetRequest(in,out);
		}
	}

}